home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The 640 MEG Shareware Studio 2
/
The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO
/
clang
/
ctask.zip
/
CTASK.DOC
< prev
next >
Wrap
Text File
|
1988-07-01
|
130KB
|
2,800 lines
CTask Manual Version 1.1 88-07-01 Page 1
CTask
A Multitasking Kernel for C
Version 1.1 Released 88-07-01
Public Domain Software written by
Thomas Wagner
Patschkauer Weg 31
D-1000 Berlin 33
West Germany
BIXmail: twagner
Introduction
============
CTask is a set of routines that allow your C program to execute
functions in parallel, without you having to build in sophisti-
cated polling and switching schemes. CTask handles the switching
of processor time with a priority based, preemptive scheduler, and
provides a fairly complete set of routines for inter-task communi-
cation, event signalling, and task interlocking. CTask also in-
cludes a number of drivers for MS-DOS that build on the basic
functions to allow you to include serial I/O, printer buffering,
and concurrent access to DOS functions into your programs with
little programming effort.
An Example
To illustrate one possible use of CTask, let me elaborate on the
following example. Say you just finished your nifty telecommuni-
cations program, complete with download protocols, scripts, and
everything. But wouldn't it be nice to be able to print the file
you just downloaded while receiving the next, and edit a comment
to some previous message without interrupting the transmission? So
you take those editor routines from a previous project, plug them
in, and - oops, how do you switch back and forth between editing,
communication and printing? The answer to this is CTask. CTask
allows your C program to do many different things at the same time
by switching the processor between the tasks you define. And since
most of the time your program is waiting for some slow device
(like the human hand) to provide feedback, this switching is com-
pletely transparent, and will not noticeably slow your program
down.
Switching the Context
So what is needed to allow the user to edit a file while at the
same time downloading another and printing a third? First, you
have to have some form of *context switching*. This means that you
have to be able to interrupt the processing of the download when
CTask Manual Version 1.1 88-07-01 Page 2
the user presses a key, process the key, and return to the down-
load *task* at the exact same point it was interrupted. One
solution to this would be to include a poll for the keyboard at
several points in the download routine, and call the editor *task*
when a key is available. But apart from cluttering your code with
lots of unrelated calls, there is another problem. What if the
operation the user requested is more involved than just putting
the character on the screen, like writing the file to disk? This
might take so long that your download times out. There must be a
way to pass control back and forth between the two tasks, such
that no task is delayed for an extended period of time, and also
to activate the print spooler task at some defined interval to
output the data to the printer. This context switching is called
*scheduling* in CTask. The *scheduler* is invoked on every system
timer tick, and will save the context of the current task. The
scheduler then takes the first element from the queue of tasks
that are eligible to be run, and restores the context of this
task, returning to the point where the task was interrupted. This
switching is completely automatic, and requires no special pro-
gramming in the tasks itself. All you have to do is to tell CTask
that there are three tasks, the download task, the spooler task,
and the editor task.
You have Mail
All you have to do for context switching, that is. There's a bit
more to multitasking than meets the eye. How do you tell the
spooler task what files to spool, and the download task what files
to download? You can't call a task like an ordinary function, so
what you need for this is *inter-task communication*. There must
be a way to pass a message containing the filename to be printed
to the spooler task, and there are several in CTask, one of them
the *mailbox*. The spooler can use a CTask call, wait_mail, to
wait for a message to arrive at its mailbox. As long as nothing
arrives, the spooler task will no longer be scheduled, so if there
is nothing to print, it will not use any processor time. When you
send a message with send_mail to the mailbox, the spooler will
wake up, and process the file. You can also send more file name
messages to the spooler while it still prints a file, leaving the
messages in the mailbox until the spooler is ready to process the
next file.
Reentrancy and Resources
This last example seems innocent enough, but there's a big
stumbling block hidden in it. You allocate the file name messages
in the controlling task with malloc, and you free them in the
spooler with free, no problem, right? Wrong, there is a big
problem, *reentrancy*. Reentrancy means that you can re-enter a
routine while another task is already using it, and that this will
not disturb the operation of the interrupted task. But malloc and
free share and modify global data, the chain of free memory
blocks. Imagine the following: You just called malloc from the
controlling task. Malloc has loaded the address of a free element
CTask Manual Version 1.1 88-07-01 Page 3
into a local variable, and is about to write back the pointer to
the next free element into the last. At exactly this moment, the
timer ticks, and the spooler is activated. It has just finished
printing, so it calls free. Free steps through the chain of free
blocks to find the right place to insert the block. According to
Murphy's law, it will find just the place where malloc is about to
write back the pointer. Free coerces the elements, points the next
pointer to the element malloc just wants to take off the chain,
and returns. Malloc writes its next pointer into the middle of the
newly coerced block, and now returns an element which is still in
the free list. Compared to the job of finding this kind of bug,
stepping in for Tantalus may feel like a vacation. This kind of
problem code is called a *critical region*. There must be a way
to make sure that no two tasks simultaneously enter such a region,
and, you guessed it, CTask provides one, the *resource*. When you
call request_resource in one task, all other tasks trying to
request the same resource after that are put to sleep until you
call release_resource. Only then will the highest priority task
that waits for the resource wake up, and get access to the protec-
ted region. So you would have to substitute malloc and free calls
in your routines with calls to functions that first request a
resource, execute the function, and then release the resource.
DOS Access
But, you might ask, isn't there another reentrancy problem in this
example, since both the spooler and the download task might simul-
taneously call DOS to do their file-I/O, and DOS is not reentrant?
Do I have to substitute all my calls to fread and fwrite, too? The
answer to this, luckily, is no. CTask traps all your DOS calls,
and automatically encloses them in the necessary resource request
and release calls, so you don't have to worry about trashing your
disk by simultaneous DOS requests. The limited multitasking capa-
bilities of DOS are exploited to allow some parallel processing in
DOS, and CTask will also detect and handle DOS calls by resident
background programs like the DOS PRINT utility.
Piping the Keyboard
CTask also allows you to circumvent DOS for keyboard input, so
that waiting for the keyboard will not block other tasks from
access to DOS functions. CTask uses another form of inter-task
communication for storing keys entered on the keyboard, the
*pipe*. The pipe is similar to the mailbox in that you can wait
for items to be sent to a pipe. But unlike mailboxes, pipes use
their own buffer to store the items (which are limited to bytes
and words), so you don't have to allocate mail blocks for each
item. When waiting on the pipe, your task is put to sleep, so the
processor is free to do more interesting things than to loop
waiting for the user to press a key. When a key is pressed, it is
written to the pipe, waking your task.
CTask Manual Version 1.1 88-07-01 Page 4
Serial I/O and Timeouts
Pipes are also used for the serial I/O handler included with CTask
that makes some of the work you've put into your communications
package obsolete. When outputting data to the serial port via the
CTask routines, the data is buffered in a pipe, and incoming data
is also placed in a pipe. All interrupt handling, and the pro-
cessing of modem status and XON/XOFF protocols, is done by CTask,
so you can concentrate on implementing the higher level protocols.
Since CTask allows all calls that wait for pipes, mail, and other
events, to specify a timeout that is based on the system tick, you
do not have to resort to timed waiting loops to detect communi-
cation line faults.
Priorities
If the protocol you implement requires fast responses to incoming
blocks, you can influence the response of CTask to your comm
task's needs by giving this task a higher priority. CTask allows
65535 different priority levels, and tasks having higher priority
are scheduled before tasks with lower priority. Also, high
priority tasks will get access to mail, pipes, and resources,
before other tasks. It might even be sensible to split the comm
task into two separate tasks, one of high priority that assembles
the incoming bytes into blocks and handles the protocol, and a
lower priority task that reads the received blocks from a mailbox
and stores them on the disk. In extremely time critical applicati-
ons, you can even turn off task preemption, so the timer tick will
no longer cause a task switch.
Change to your liking
CTask provides all basic building blocks for implementing concur-
rent programs in an easy and comprehensible way. Since CTask is
mainly implemented in C, it may not be the fastest possible
system, but due to the straightforward design which uses few
shortcuts, modifying the sources to suit your needs and taste can
be done without weeks of studying assembler code that squeezes
every microsecond from the processor. CTask is public domain code,
and there are no restrictions on its use. It is distributed in
source form, so you are free to change all aspects of the package.
Multitasking programs, especially in embedded applications, tend
to be very diverse in their needs for specific constructs. So
although CTask is ready to run under DOS, and is easily adaptable
for embedded applications, you should see CTask more as a starting
point for your own thoughts, and as a toolbox from which you can
pick the instruments you need, than as a finished and fixed block
of code you simply plug into your application.
CTask Manual Version 1.1 88-07-01 Page 5
How does CTask work
===================
Queues
CTask uses priority queues for most of its functions. A priority
queue differs from the usual FIFO (first in, first out) queues in
the insertion of elements into the queue. Rather than just placing
new elements after the last queue member, the priority of the task
determines its place in the queue. A task is enqueued after all
queue members with higher or equal priority, but before any member
with lower priority. Removal from the queue takes place at the
first element. These queues are used with all operations in which
a task is made waiting, be it waiting to get run, or waiting for
an event. The advantage of this is the simplicity of implementing
priorities. Other schemes might rely on arrays of queues, or sear-
ching through lists, to determine the highest priority waiting
task. With priority queues, the most time critical function, sche-
duling, is very fast, since it can simply take the first task from
the queue of eligible functions. The disadvantage is the time
required to step through all greater or equal priority members of
a queue to find the right place to insert the task. But since the
number of tasks simultaneously enqueued in the same queue is nor-
mally relatively small with most multitasking systems, this disad-
vantage is more than offset by the simple, and thus easy to imple-
ment efficiently, scheme. The operation of removing a task from
somewhere in the middle of a queue is rare (it will only occur on
task kill, task wake, or timeout), and thus the overhead of mana-
ging a doubly linked list is not justified.
The Scheduler
In a CTask system, there is at least one essential queue: the
eligible queue, which contains all tasks waiting to run. Each time
the scheduler is invoked, it will first save all processor regis-
ters on the current stack, save the stack pointer in the current
tasks control block, and then enqueue the current task into the
appropriate queue. Normally, this will be the eligible queue.
Then the first element in the eligible queue is removed from the
queue, the stack is switched to the stack of this task, and pro-
cessor registers are restored from the new stack, returning to the
point where the new task was interrupted. If the eligible queue is
empty the scheduler will loop with interrupts enabled. The queue
head pointer of the task control block of the current running task
determines what will happen to the task when the scheduler is
invoked. If it points to an event control block, the task will be
enqueued as waiting for this event. If it is NULL, the task will
not be enqueued in any queue, effectively stopping it. If it
points to the eligible queue, the task will be enqueued there. In
any case, the scheduler does not care what queue the task is to be
enqueued in, since all queues use the same priority based order-
ing. If a task is no longer in the eligible queue, it can only be
returned to this queue by an external event.
CTask Manual Version 1.1 88-07-01 Page 6
Events
External events can take several forms, which are relatively
similar on the inside. All CTask events use a control block, with
at least one queue for tasks waiting for the event. Although it
would have been possible to summarize all events under a global
structure, this approach was not used in CTask, the main reasons
being execution speed and ease of use. So if you scan through the
source files for CTask events, you will see many very similar
routines, which only differ in the types of data and the names of
queues they process. In a class based language like C++, one would
certainly define all events as instances or derivations of one
class. In plain C, the necessary type casting, and the different
handling of certain cases, would clobber the code to the point of
illegibility.
Resources
The event types "resource", "flag", and "counter" differ only in
the kind of wait operations and the handling of the state vari-
able. The resource is mainly for use in interlocking critical
regions of code, to protect access to non-reentrant resources.
Only one task can "own" a resource, all other tasks requesting the
resource are delayed until the owning task releases it. For added
protection, CTask stores the task control block address of the
current owner of the resource in the resource control block, so no
other task can erroneously release it.
Flags
Flags, on the other hand, can be set and cleared by any task, and
also by interrupt handlers. Tasks can wait on either state of the
flag, and all tasks waiting for a state are simultaneously acti-
vated when the flag is changed to this state. This makes flags
suitable for use as a global signalling mechanism.
Counters
Counters are a variation on the flag concept. A counter can be
incremented and cleared by any task, and by interrupt handlers.
Tasks can wait for a counter to be zero or nonzero. Like flags,
all tasks waiting for the zero state are activated simultaneously.
But unlike flags, only the first task waiting for the nonzero
state of a counter will be activated on incrementing the counter,
and the counter will be automatically decremented by one. The
counter is used inside CTask to handle timer interrupts. The timer
counter will be incremented on each timer tick, activating the
timer task. If for any reason the timer task is unable to complete
its run until the next tick, this tick will not be lost, since the
counter is incremented, and the timer task will continue to run
the next time it calls the counter wait request.
CTask Manual Version 1.1 88-07-01 Page 7
Mailboxes and Pipes
The "mailbox" and "pipe" events can be used for inter-task
communication, and for the communication between interrupt
handlers and tasks.
Mailboxes
Mailboxes can hold an unlimited number of mail blocks. Mail blocks
have no fixed structure, but the first doubleword in each block
passed to a mailbox routine is used as a chain pointer. Mail
blocks are chained into a mailbox in FIFO order. Tasks can wait
for mail to arrive, or can conditionally read mail if a block is
available. Tasks and interrupt handlers can write blocks to a
mailbox. Since mailboxes don't need any copying of data, they are
suited for high speed exchange of larger amounts of data between
tasks. The disadvantage of sending mail this way is that you have
to make sure that a mail block is not re-used before it has been
read out of the box and processed by the receiving task. When
exchanging fixed length messages, you can build a free mail block
chain by using a mailbox to hold the available blocks.
Pipes and Buffers
Buffered message exchange is possible using pipes. When creating a
pipe, you specify a buffer area and its length, and the pipe
routines will buffer all data written to the pipe in this area.
This implies that writing to a pipe may cause a task to be delayed
until there is space available in the pipe. To allow interrupt
handlers to write to pipes, there is a conditional write request,
which will simply return if the pipe is full. Tasks can wait for
data to arrive in a pipe, and for the pipe to be emptied. A
conditional read request is also provided. The disadvantage of
pipes is that they are slightly slower than mailboxes due to the
necessary copying, and that you can only place word or byte sized
items in a pipe. When there is more than one reader or writer
task, you can not rely on the bytes in a pipe being in any
specific order. A "buffer" construct is provided in CTask that
expands pipes to allow arbitrary length messages to be written and
read. This is implemented using a resource for reading and writing
to the pipe associated with the buffer, so the message is always
guaranteed to be written and read in one piece. But since
resources can not be used in interrupt handlers, using such
buffers is not allowed from interrupts.
Applications
There are a number of routines included in the CTask package for
PC-specific tasks. Although they will be of limited use for embed-
ded applications, studying the serial I/O and printer interface
routines will give you some hints on how to use the CTask kernel
for implementing your own device drivers. For PC based appli-
cations, the routines should be usable with no or little changes
for implementing complete communications packages.
CTask Manual Version 1.1 88-07-01 Page 8
The serial I/O handler uses interrupts for both input and output
of data, and supports both XON/XOFF and RTS/CTS handshake methods.
The modem inputs can selectively be enabled to control data trans-
mission. Pipes are used for transmit and receive data, with the
buffer and its size specified on initialization. Both COM1 and
COM2 can be active simultaneously, and other ports can be suppor-
ted by editing a table in the source code. Since CTask allows the
access to all events in interrupt handlers, writing interrupt
based I/O drivers is relatively simple. On receiving a character,
the character and any associated errors are placed in the receive
pipe, and the transmit pipe is read on transmit ready interrupt.
Note that conditional reads and writes have to be used in the
interrupt handler, since you can't safely delay an interrupt
handler.
The printer output driver supports both polling and interrupt
output. Again, a pipe is used for the output characters. However,
since polling is supported, and because of the somewhat unreliable
interrupt structure of the printer ports, a driver task is re-
quired. This task is automatically created on installing the
driver. The task first waits on the pipe for a character to be
written to the printer. When polling is enabled, it tries to
output the character directly, using a short busy-waiting loop. So
as not to load the system too heavily by the polling, the task
will delay itself for a defined amount of timer ticks if it can't
output the character after a small number of loops. When inter-
rupts are enabled, the process is essentially the same at the
start, but after the character is written to the port, the task
will wait on a flag to be set. The interrupt handler will set the
flag on interrupt, enabling the task to get the next character
from the pipe. Since interrupts are unreliable with most printers
due to the usually very short pulses on the acknowledge line, the
task uses a timeout on the flag wait, so it does not hang if an
interrupt is missed. Studying the printer driver will also give
you an idea what the optional parameter on task creation can be
used for. In the printer driver, this parameter is used to pass
the address of the printer control block, which contains the pipe,
the flag, and the hardware info, to the task. This allows the
printer driver to be simultaneously installed for any number of
printers without having to write separate printer tasks.
CTask Manual Version 1.1 88-07-01 Page 9
General Notes
=============
What can CTask NOT be used for?
CTask is not intended to provide for multitasking on the command
level of MS-DOS. Although CTask includes a module for channeling
simultaneous DOS requests inside a program, the strategy used in
this module is not sufficient for the functionality required when
switching between programs. Adding this functionality would not be
trivial (although certainly worthwhile).
Also, there is no warranty that CTask does perform without errors,
or does exactly what you or I intended. So CTask should not be
used in applications in which malfunction of routines of this
package would result in damage to property or health of any person
without *very* extensive testing under all kinds of loads. In
using CTask, you do so at your own risk.
What is required to use CTask?
To compile CTask, Microsoft C 5.0 or later, or Turbo C 1.0 or
later are required. Microsoft MASM 5.0 or later is required for
the assembler parts. Conversion to other compilers is possible if
they conform to the new ANSI (draft) standard. Conversion of the
assembler parts to other Assembler versions requires substitution
of the simplified model directives by explicit segment defi-
nitions, and adding the DGROUP to offsets referencing data.
CTask will add 8-12k of code to your program, depending on options
and installed drivers. The minimum static data used by CTask is
approximately 4k.
Converting CTask for stand-alone operation requires few changes.
Mainly, the timer interrupt handler (in "tsktim.asm") has to be
rewritten, and the initialization code (in "tskmain.c") may have
to be changed. Changes to other modules (naturally except the
optional hardware drivers) should not be necessary.
Another requirement is a good debugger. If you never before wrote
multitasking applications, you're in for some surprises. The
normal debugging tools (Symdeb, Codeview) are of only limited use,
since they use DOS calls for their I/O, and thus may conflict with
your background tasks. One safety measure is to first thoroughly
test your program with preemption disabled, possibly inserting
some schedule() calls, and only allow task preemption if you found
most major bugs. I personally recommend Periscope for debugging,
since it can be made resident and so is always available, and
because it does not use DOS. Periscope III is the most expensive
solution, and the best tool you can imagine (except for Periscope
IV, announced for April), but the less costly versions will also
help a lot.
CTask Manual Version 1.1 88-07-01 Page 10
Do I have to pay for using CTask?
No. One reason for writing CTask was to provide a free, no strings
attached, utility, instead of the usual "for personal use only"
restriction. Writing a multitasking application for personal use
only doesn't seem too interesting to me. CTask is completely free,
and there is no restriction on its use. You may incorporate all or
parts of CTask in your programs, and redistribute it in source or
binary form by any means. I also do not restrict the use of CTask
in commercial applications. Since trying to distribute the
unmodified CTask for money will only give you a bad name, you may
even do that if you find someone dumb enough to buy it. Naturally,
if you make a bundle from it, or simply like CTask, I would not
reject a donation. However, this is not required, and it will not
give you any additional support.
What support can I expect?
I will try my best to eliminate any bugs reported to me, and to
incorporate suggested enhancements and changes. However, my spare
time is limited, so I can not guarantee continued or individual
support. (But since I am a free-lance consultant, you can always
hire me to do it...)
At the time of this writing, I'm connecting to BIX almost daily.
Problems of limited general interest can be reported via BIXmail,
suggested enhancements and changes, plus bug reports, should be
posted in the c.language/tools conference on BIX (until responses
warrant a new conference, or I can't afford BIX any longer), so
they can be discussed among all interested (I hope there will be a
few). Normal mail can be used, too, but don't expect fast
responses.
About this Release
Since the Beta release of CTask in March, CTask has been down-
loaded from BIX more than 90 times. Of all downloaders, just six
commented on CTasks features, or sent bug reports. The ideas
presented by the commentors are mainly integrated in this version,
as are the fixes for the reported bugs. But since comments were
sparse, this is not a revolutionary new release. Many areas are
completely unchanged, or just slightly modified to accommodate new
flags and options. See the accompanying file "changes.doc" for
a summary of the changes.
Although I currently have no new ideas that would warrant another
release, I again would like to invite you to voice your com-
plaints, suggest enhancements, supply your own code for inclusion
in the package (note that any code submitted for inclusion must
not be copyrighted or usage restricted, but I'll naturally respect
copyrights in code submitted for demo purposes), or suggest
additions to this manual.
CTask Manual Version 1.1 88-07-01 Page 11
Multitasking Basics
===================
Tasks
In CTask, a "task" is defined as a (far) C function. The number of
tasks is not limited, and one function may be used for several
tasks. There is little difference between a task function and a
normal function. The usual form of a task function is
void far my_task (farptr arg)
{
one-time initialization code
while (TRUE)
{
processing code
}
}
A task function is (usually) never called directly. Rather, it is
specified in the call to the create_task routine, and started by
start_task. It will then continue to run, sharing the processor
time with all other tasks, until it is "killed" by kill_task.
Returning from the routine will have the same effect as a kill.
The sharing of processor time is accomplished by "preempting" the
tasks. Preemption means that the task is interrupted in the middle
of some statement by a hardware interrupt (usually the timer), and
is *not* immediately restarted when the interrupt handler returns.
Instead, the next task that is able to run is activated by the
"scheduler", with the interrupted task continuing its duty at some
(normally unpredictable) later time. You can also have multi-
tasking without preemption, and CTask supports this, too, but this
requires full cooperation of all tasks in the system, such that no
task continues to run for an extended period of time without
passing control to other tasks by an explicit scheduling request,
or by waiting for an event.
The optional argument to the task function may be used if one
function is to be used for more than one task, for example to pass
a pointer to a static data area for use by this specific instance
of the function.
CTask Manual Version 1.1 88-07-01 Page 12
Events
Tasks alone would be of limited use. If you have several routines
which just do number crunching or sorting or such, making them
into parallel tasks would be sensible only on a multiprocessor
system. Normally, at least some of your tasks will wait for some
outside "event" to happen, be it the user pressing a key, or a
character arriving from the modem. Then there may be tasks which
have to wait until another task finishes processing on some piece
of data before they can continue. For this synchronization, there
are a number of constructs in CTask, which I summarize under the
name "event". Common to all events are the operations of waiting
for an event to happen, and signalling that the event has
happened. Using a CTask event is much more efficient than looping
while waiting on some shared data location to change state, and
also eliminates concurrency problems inherent in such a simple
approach. Tasks waiting for an event are taken off the scheduler
queue, so they no longer use processor time.
Reentrancy
One of the biggest problem with multitasking in general, and C on
the PC in particular, is reentrancy. Reentrancy means that you can
use a routine, be it you own, or one of the C run-time library,
from different tasks at the same time. When writing your own code,
you can easily avoid problems, but when using the run-time library
routines, you often can only guess if the routines are reentrant
or not.
A routine is NOT reentrant if it modifies static data. This can be
illustrated by the following nonsense example:
int non_reentrant (int val)
{ static int temp;
temp = val;
return temp * 2;
}
Now take two tasks, which call this routine. Task1 calls it with
val=3, Task2 with val=7. What will be the return value for both
tasks? You never know. There are three possible outcomes:
1) The tasks execute sequentially. Task1 will get 6, and Task2
14 as a result. This is what one normally expects.
2) Task1 runs up to "temp = val", then is interrupted by the
timer. Task2 executes, and gets 14 as result. Then Task1
continues. Return for Task1 is 14.
3) Task2 runs up to "temp = val", then is interrupted by the
timer. Task1 executes, and gets 6 as result. Then Task2
continues. Return for Task2 is 6.
CTask Manual Version 1.1 88-07-01 Page 13
add to this the effects of optimization, and a loop, and the
outcome is completely random.
Most routines in the C library will not explicitly do something
like this, but all functions that
- do file I/O (read, write, printf, scanf, etc.) or
- change memory allocation (malloc, free, etc.)
have to use some static data to do buffering, or to store the
chain of memory blocks in. Interrupting such an operation may have
disastrous effects. The most devilish aspect of non-reentrancy is
that the effects are unpredictable. Your program may run 1000
times without any error, and then on the 1001th time crash the
system so completely that only the big red switch will help.
So what can you do about it? A lot. There are several ways to
protect "critical regions" from being entered in parallel. The
most simple method is to disable interrupts. This, however, should
be used only for *very* short periods of time, and not for rou-
tines that might themselves re-enable them (like file-I/O). A
better method is to temporarily disable task preemption. The
routines tsk_dis_preempt and tsk_ena_preempt allow this form of
short-term task switch disable. Interrupts may still be processed,
but tasks will not be preempted. The best way is to use a
"resource". A resource is a special kind of event, which only one
task can possess at a time. Requesting the resource before
entering the routine, and releasing it afterwards, will protect
you from any other task simultaneously entering the critical
region (assuming that this task also requests the resource).
It is also reasonably safe to use file-I/O without protection if
it goes to different files. The C file control blocks for
different files are distinct, so there will be no conflict between
tasks. Since DOS access is automatically protected by CTask,
concurrent file I/O is possible.
What you may NEVER do is to use a non-reentrant routine that is
not protected by an interrupt disable from an interrupt handler.
An interrupt handler is not a task, and so can not safely request
a resource or disable task preemption. This is the reason why the
CTask routines generally disable interrupts before manipulating
the internal queues rather than only disabling task preemption.
Deadlocks
One thing to watch out for when using resources or similar event
mechanisms is not to get into a situation where Task 1 waits for a
resource that Task 2 has requested, while at the same time Task 2
waits for a resource that Task 1 already has. This situation is
called a deadlock (or, more picturesque, deadly embrace), and it
can only be resolved with outside help (e.g. waking up one of the
tasks forcibly). To illustrate, consider the following example:
CTask Manual Version 1.1 88-07-01 Page 14
void far task_1 ()
{
...
request_resource (&rsc1, 0L);
request_resource (&rsc2, 0L);
...
}
void far task_2 ()
{
...
request_resource (&rsc2, 0L);
request_resource (&rsc1, 0L);
...
}
Since interrupts are always enabled on return from a task switch,
even if the statements are enclosed in a critical region, there is
no guarantee that the request_resource calls will be executed
without interruption. In this example, the problem is obvious, but
in a more complex application, where resource requests or other
waits might be buried in some nested routine, you should watch out
for similar situations. One way to avoid problems would be in this
example to change task_2 to
void far task_2 ()
{
int again;
...
do {
request_resource (&rsc2, 0L);
if (again = c_request_resource (&rsc1))
{
release_resource (&rsc2);
delay (2L);
}
} while (again);
...
}
Note that this is only one of many possible approaches, and that
this approach favors task_1 over task_2.
You should also take care not to kill tasks that currently own a
resource. CTask will not detect this, and the resource will never
be freed.
CTask Manual Version 1.1 88-07-01 Page 15
Multitasking and DOS
CTask includes (and automatically installs) a routine which traps
all DOS calls, and makes sure that no two tasks simultaneously
enter DOS. This is accomplished using the resource mechanism, with
special provisions for the limited multitasking capabilities
provided by DOS. There are a few calls, namely those with function
codes <= 0x0c, which allow functions with codes > 0x0c to be
executed while DOS is waiting for an external device (generally
the keyboard) to get ready. This, however, limits the use of some
C library functions, namely scanf and fread, for console input.
Both these functions use handle input, and thus can not be
interrupted. When writing routines for handling user input,
keyboard read functions should either use the low-level calls
getch and gets, or, better yet, the direct entries into the
keyboard handler, t_read_key and t_keyhit, and then process the
string with sscanf if desired. The keyboard handler (contained in
tskkbd.asm) traps all keyboard interrupts, storing entered keys in
a pipe. If a task reads from the keyboard, it is automatically
waiting for this pipe. Using getch and gets is less desirable
since they use polling instead of waiting, and thus degrade system
performance. Also, the t_read_key and t_keyhit functions do not
use DOS, so DOS functions <= 0C can be executed concurrently.
You should NEVER circumvent DOS by calling the BIOS-disk-I/O
function (INT 13) directly. This entry is not protected by CTask,
and using it in parallel to DOS file-I/O may send your hard-disk
FAT and directory into never-never land. If you really should need
this interrupt, you should consider adding support for it in the
DOS-handler module "tskdos.asm". Using the direct sector read and
write interrupts 25 and 26 is supported, however. Using other BIOS
interrupts directly should also be avoided, unless you are
absolutely sure they will not be used by other routines via DOS,
or you provide your own critical region handling. Using interrupt
16, the keyboard interrupt, is safe, since it is redirected to the
keyboard handler pipe.
The DOS access module has been tested to work with DOS 3.30, and
with the DOS 3.30 PRINT routine running in the background. Special
provisions are built into the DOS module to detect background DOS
calls. Using Sidekick also hasn't lead to any problems, although
you may trash Sidekick's screen display by writing to the screen
while Sidekick is active (note that your application *continues*
to run while Sidekick or other pop-ups are active). I can not
guarantee complete compatibility with all background and pop-up
programs under all versions of DOS. Specifically, DOS versions
prior to 3.1 have significant problems with multitasking.
Upgrading to a newer DOS version is recommended if you are still
using DOS < 3.2 (DOS 3.1 has some bugs in other areas).
Critical errors and Control C occurring while concurrent DOS
access takes place may also be fatal. Using the ctrlbrk() and
dosexterr() functions of Turbo C, or their equivalents in MS C, to
trap critical errors and Control C is highly recommended.
CTask Manual Version 1.1 88-07-01 Page 16
Using CTask
===========
CTask comes archived with both source and binaries. The binary
version is compiled in the large model, but since the pre-compiled
kernel routines don't use any functions from the C library, you
can use all functions in small or other model programs (except
Tiny). The include files provided specify all model dependencies,
so you don't have to use the large model for your application, but
always remember to include "tsk.h" for the type and routine proto-
types. The C source files will work without changes for both
Microsoft and Turbo C. The library files are not compatible, so
use "ctaskms.lib" for Microsoft, and "ctasktc.lib" for Turbo C. In
the distributed configuration (i.e. dynamic allocation of control
blocks enabled), the file TSKALLOC.C must be added to the library
or separately linked after compiling it *in the same model as the
main program*. This file uses C-library routines, and thus must
match the main program's memory model. The same goes for
TSKSNAP.C, an optional snapshot-dump utility.
Configuration Options
The file TSKCONF.H contains a number of #define's that allow you
to configure some CTask features. The entries are
TSK_DYNAMIC
If 1, you can let CTask dynamically create task and event
control blocks, task stacks, and pipe buffers, by passing
NULL as the block address. Since this requires the C runtime
allocation calls, it is not suitable for non-DOS applications
(except if you provide your own memory allocation routines).
This option is normally enabled (1).
Changing this option requires changing the corresponding
option in the Assembler macro file TSK.MAC.
TSK_NAMEPAR
If 1, all create_xxx calls accept an additional parameter,
the name of the created control block. This name should con-
tain only printeable characters, and should not be longer
than 8 characters plus the zero terminator. This option is
used together with TSK_NAMED (see below).
This option is normally enabled (1).
Changing this option requires changing the corresponding
option in the Assembler macro file TSK.MAC.
TSK_NAMED
If 1, all control blocks (except timer control blocks) are
named and linked on creation. This allows the snapshot dump
CTask Manual Version 1.1 88-07-01 Page 17
routine to display the system state. TSK_NAMEPAR must be
defined for this option to work. Since it may be desirable to
switch off the handling of the names after the debugging
phase, without having to change all create_xxx calls to omit
the name parameter, the name parameter is ignored if
TSK_NAMED is undefined, but TSK_NAMEPAR is defined.
This option is normally enabled (1).
Changing this option requires changing the corresponding
option in the Assembler macro file TSK.MAC.
CLOCK_MSEC
If 1, all timeouts are specified in milliseconds instead of
timer ticks. This allows programming of delays and timeouts
independent of the speedup-parameter or the system tick rate.
Since timeout calculations use floating point operations if
this option is enabled, a floating point library is needed.
This precludes model independence of the CTask kernel.
This option is normally disabled (0).
PRI_TIMER
Specifies the priority of the timer task. Normally the timer
task should have a higher priority than any other task in the
system.
The value is normally 0xf000.
PRI_STD
This value may be used in your programs as the standard
priority of user tasks. Its value is arbitrary. It is not
used in the kernel except for the definition of PRI_INT9 (see
below).
The value is normally 100 (decimal).
PRI_INT9
Determines the priority of the "int9"-task. This task chains
to the previous interrupt vector for the sytem timer tick,
and thus may activate resident TSR's. Since TSR's normally
use polling when accessing the keyboard and other devices,
the priority of the int9-task should be equal to or lower
than normal user-defined tasks to allow your program to con-
tinue to run while the TSR is active. You can tune this value
so that some tasks are blocked by the TSR to avoid trashing
the screen.
The value is normally PRI_STD - 1.
CTask Manual Version 1.1 88-07-01 Page 18
IBM
DOS
Both IBM and DOS are more or less of informative value only,
to point out those areas in the kernel that need attention
when converting to non-IBM or non-DOS environments. Disabling
one or both of this options requires the substitution of your
own routines for installation and keyboard handling.
Both options must normally be enabled (1).
AT_BIOS
If enabled, the AT BIOS wait/post handler is installed.
May be disabled when compatibility problems arise.
This option is normally enabled (1).
Memory Allocation
TSKALLOC.C is needed if TSK_DYNAMIC is enabled in tskconf.h to
handle the allocation and free calls. If you want to use dynamic
allocation in your own tasks, you should also use the functions
tsk_alloc and tsk_free to avoid the reentrancy problems mentioned
in the introduction. If you should need other forms of alloc (like
calloc) the recommended way is to add those functions to
TSKALLOC.C, requesting the resource alloc_resource before calling
the C memory-allocation function.
Snapshot
TSKSNAP.C is only needed if you want to include the snapshot dump
into your program. Note that you can *not* use snapshot if you
disable TSK_NAMED in tskconf.h.
Task Stacks
When compiling your application, turn stack checking off. The
standard stack check is of little use with task stacks, and may
interfere with CTask's operation. The stack area for tasks should
be allocated on the main program's stack, not in the static or
heap data space. The reason for this is that some of the C library
routines check for stack overflow regardless of your compile-time
switches, and will crash your application if you use stacks
outside the normal stack. The stack allocation parameter with LINK
(MS-C), or the _stacksize variable (Turbo C) have to be increased
to reflect the additional stack space. When calculating task stack
sizes, keep in mind that library routines (esp. printf) allocate a
lot of space on the stack for temporary variables. A minimum of 1k
for tasks using library routines is recommended, 2k puts you on
the safe side. Tasks not using C library routines may use a
smaller stack, about 256 bytes at a minimum, plus space for any
CTask Manual Version 1.1 88-07-01 Page 19
local variables and nested routines. Then add up all task stacks,
and add space for the main task (the function calling
install_tasker), with this size also dependent on what you will do
in the main task while CTask is active. Stacks for tasks that do
not use C library routines may be allocated anywhere.
Drivers
The keyboard and DOS handlers are always installed with CTask.
Using the serial I/O and printer drivers is optional, so you have
to install them separately, but only *after* installing CTask.
When using the serial driver, include "sio.h" in your modules,
when using the printer driver, include "prt.h".
Another driver that is automatically installed is the BIOS
wait/post handler for the IBM AT. The AT BIOS contains some
multitasking hooks to avoid busy waiting in the BIOS. If you
experience problems with disk accesses or printer output through
BIOS (not through the CTask printer driver), you should disable
installation of this driver by setting AT_BIOS to zero in
tskconf.h. Normally, no problems should arise with this driver
even in XT type machines.
Things to remember
Remember that tasks are not automatically started after creation.
Use start_task to allow a created task to run.
Always use create_xxx on resources, pipes, etc. Using the event
routines without doing so will have unpredictable results.
Before exiting the program, all installed drivers and CTask should
be explicitly removed. Although the DOS handler traps the
terminate call and automatically calls remove_tasker, you should
make sure that all tasks are completed properly, and call
remove_tasker yourself.
Deleting events before exiting the program is not mandatory, but
recommended to kill all tasks waiting for the event. You should be
careful not to kill tasks while they are active in DOS. The
kill_task routine should be reserved for fatal error handling. The
best way is to let the tasks kill themselves depending on some
global variable or event. If a task is killed while waiting for
input in DOS, DOS operation may be severely impaired. If you use
the C console input routines, make sure that the task returns from
DOS before it is killed, if necessary by requesting the user to
press a key.
CTask Manual Version 1.1 88-07-01 Page 20
Priority Handling
=================
CTask provides for prioritized task execution and event
processing. This means that a task that has a higher priority will
be run before any other tasks having lower priority. Also, a
higher priority task will gain access to resources, counters,
pipes, and mail, before lower priority tasks. With fixed
priorities, this means that a high priority task can monopolize
CPU time, even if it calls schedule() explicitly. Variable
priority increases each eligible task's priority by one on each
scheduler call, so that lower priority tasks will slowly rise to
the head of the queue until they get executed. The priority will
be reset to the initial priority when a task is run.
Since variable priority increases processing time in the critical
region of the scheduler, it is not recommended for systems in
which a larger number of tasks is expected to be eligible
simultaneously.
Usually, all tasks in a system should have the same priority,
with only very few exceptions for non-critical background
processing (low priority) or very time-critical tasks (high
priority). High priority tasks should be written in such a way
that they either reduce their priority when processing is
completed, or that they wait for an event. Busy waiting in a high
priority task will severely impair system operation with variable
priority enabled, and will stop the system until the task is
placed in a waiting state with fixed priority.
The (automatically created) main task is started with the highest
possible priority below the timer task, so that it can process all
initialisations before other tasks start running. To allow the
system to operate, the priority of the main task must be reduced,
or the main task must wait for an event or a timeout.
CTask Manual Version 1.1 88-07-01 Page 21
CTask Data Types
================
Note that you do not have to know the innards of the structures.
All structure fields are filled by the "create_xxx" routines and
modified by the CTask functions. You should NEVER modify a field
in one of the structures directly. The structures are explained
here shortly only for those wanting to modify the routines.
If you only want to use the routines, you should simply include
the file "tsk.h" in your source, and define variables of the types
tcb - for task control blocks
tcbptr - for far pointers to tcbs
flag - for flag events
flagptr - for far pointers to flags
resource - for resource events
resourceptr - for far pointers to resources
counter - for counter events
counterptr - for far pointers to counters
mailbox - for mailbox events
mailboxptr - for far pointers to mailboxes
pipe - for pipe events
pipeptr - for far pointers to pipes
wpipe - for word pipe events
wpipeptr - for far pointers to word pipes
buffer - for buffer events
bufferptr - for far pointers to buffers
tlink - for timeout control blocks
tlinkptr - for far pointers to timeout control blocks
namerec - for control block names
nameptr - for name pointers
without caring what's behind them.
Additionally, you may use the types
byte - for unsigned characters
word - for unsigned short integers
dword - for unsigned long integers
funcptr - for pointers to task functions
farptr - for far pointers to anything
byteptr - for far pointers to byte arrays
wordptr - for far pointers to word arrays
CTask Manual Version 1.1 88-07-01 Page 22
in defining or typecasting items to be passed as parameters to
CTask functions.
Typedefs used for simplified type specifications
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;
typedef void (cdecl far *funcptr)();
typedef void far *farptr;
typedef byte far *byteptr;
typedef word far *wordptr;
Error return values for mailbox functions
#define TTIMEOUT ((farptr) -1L)
This value is returned if a timeout occurred during a mailbox
wait.
#define TWAKE ((farptr) -2L)
This value is returned if the task was waked up during a
mailbox wait.
The timer control block
The timer control block is included in every task control block.
It may also be created as a separate entity to specify special
actions to be executed after or every n timer ticks. The "next"
field links the active structures into the timer queue.
"timeout" gives the number of timer ticks, "reload" is used for
repetitive timeout actions to reload the original timeout value.
"strucp" points to the structure to be acted upon, "tkind"
specifies the kind of structure:
#define TKIND_TASK 1
Strucp points to the task control block of which the
element is a member. The task will be awakened when the
timeout expires.
#define TKIND_WAKE 2
Strucp points to a task control block. Otherwise same as
TKIND_TASK.
#define TKIND_PROC 3
Strucp contains the address of a function to be called
on timeout.
#define TKIND_FLAG 4
Strucp points to a flag that should be set on timeout.
CTask Manual Version 1.1 88-07-01 Page 23
#define TKIND_COUNTER 5
Strucp points to a counter that should be increased on
timeout.
#define TKIND_TEMP 0x80
This is a modifying flag. If set, the timer control
block was allocated dynamically, and should be freed on
timeout.
"tstate" contains the state of the element:
#define TSTAT_REMOVE -1
The element should be taken off the queue on the next
pass (if the TKIND_TEMP-flag is set, it will be freed).
#define TSTAT_IDLE 0
The element is not enqueued.
#define TSTAT_COUNTDOWN 1
The timeout counter is counted down. If the timeout is
reached, the element is removed from the queue.
#define TSTAT_REPEAT 2
The timeout counter is counted down. If the timeout is
reached, the timeout count is reloaded.
typedef struct tlink_rec far *tlinkptr;
struct tlink_rec {
tlinkptr next;
dword timeout;
dword reload;
farptr strucp;
byte tstate;
byte tkind;
};
typedef struct tlink_rec tlink;
The name link structure
If TSK_NAMED is enabled, all structures except the timer control
block contain a name link. All control blocks are linked and named
via this element. To facilitate removal from the list, it is
doubly linked via the pointers "follow" and "prev".
"strucp" points to the head of the structure the name link is an
element of, with "nkind" specifying the type of structure:
CTask Manual Version 1.1 88-07-01 Page 24
#define TYP_TCB 1 task control block
#define TYP_FLAG 2 flag event
#define TYP_RESOURCE 3 resource event
#define TYP_COUNTER 4 counter event
#define TYP_MAILBOX 5 mailbox event
#define TYP_PIPE 6 byte pipe
#define TYP_WPIPE 7 word pipe
#define TYP_BUFFER 8 buffer
The head element of the name list has its nkind field set to zero.
The "name" field contains an up to 8-character name plus a zero
terminator.
#define NAMELENGTH 9
typedef struct name_rec far *nameptr;
struct name_rec {
nameptr follow;
nameptr prev;
farptr strucp;
byte nkind;
char name [NAMELENGTH];
};
typedef struct name_rec namerec;
The task control block structure
The "next" field points to the next tcb in a queue. The "queue"
pointer points to the head of the queue the task is enqueued in,
or will be enqueued in on the next schedule request in the case of
the current running task.
"stack" contains the saved task stack pointer (offset and segment)
if the task is not running. The field "stkbot" contains the bottom
address of the task stack. It is set by create_task, but is
currently not used anywhere else. Stack checking routines might
use this value to test for stack overflow and/or stack usage.
"prior" contains the tasks current priority, with 0xffff the
highest possible priority, and 0 the lowest. "initprior" initially
contains the same value. If variable priority is enabled, "prior"
is incremented on each scheduler call, and reset to "initprior"
when the task is activated.
"state" and "flags" contain the tasks state and flags.
"timerq" is a tlink structure used to chain the tcb into the timer
queue, if the task is waiting for a timeout. See above for a
description of the tlink structure.
CTask Manual Version 1.1 88-07-01 Page 25
The fields "retptr" and "retsize" are used in event handling. They
are used when a task is waiting for an event by the task acti-
vating the event, and also by timeout and wake to indicate error
returns. The use of these pointers eliminates the need to loop
for an event, which requires slightly more code in the event
handling routines, but reduces the need for task switching.
The "name" namerec structure is present only if TSK_NAMED is
enabled. It may be used in debugging (see tsksnap.c for an
example), and in applications where the address of the structure
can not be passed directly.
typedef struct tcb_rec far *tcbptr;
typedef tcbptr far *tqueptr;
struct tcb_rec {
tcbptr next;
tqueptr queue;
byteptr stack;
byteptr stkbot;
word prior;
word initprior;
byte state;
byte flags;
dlink timerq;
farptr retptr;
int retsize;
};
typedef struct tcb_rec tcb;
Task states
#define ST_KILLED 0
#define ST_STOPPED 1
#define ST_DELAYED 2
#define ST_WAITING 3
#define ST_ELIGIBLE 4
#define ST_RUNNING 5
CTask Manual Version 1.1 88-07-01 Page 26
Possible task states and queue association
ST_KILLED The task has been killed. Restarting the task is
not possible. Queue pointers are invalid.
ST_STOPPED The task is not enqueued in any queue. To be in-
cluded in scheduling, it has to be explicitly
started. The queue head pointer is NULL.
ST_DELAYED The task is enqueued in the timer queue only. When
the timer expires, it is placed in the eligible
queue. The queue head pointer is NULL.
ST_ELIGIBLE The task is enqueued in the queue of processes
eligible for running. It can not be chained in the
timer queue. The queue head pointer points to the
eligible queue.
ST_RUNNING The task is the current running process. Although
it is not enqueued in any queue, the queue head
pointer in its control block is valid and points to
the queue the process will be enqueued in on the
next schedule request.
ST_WAITING The task is waiting for an event to happen. It can
also be chained into the timer queue if a timeout
was specified in the call. The queue head pointer
points to the queue head in the event control block
for the event the process is waiting on.
Possible state transitions and their reasons
stopped -> eligible by start_task ()
delayed -> killed by kill_task ()
-> eligible by timer task, or wake_task ()
eligible -> killed by kill_task ()
-> running by scheduler
running -> killed by kill_task ()
-> stopped by delay (0)
-> delayed by delay (n != 0)
-> eligible by scheduler
-> waiting by wait_xxx ()
waiting -> killed by kill_task ()
-> eligible by event happening, timeout,
or wake_task()
CTask Manual Version 1.1 88-07-01 Page 27
Task flags
System flags:
#define F_TEMP 0x80
This tcb was allocated automatically, and must be free'd
on task kill.
#define F_STTEMP 0x40
The tasks stack was allocated automatically, and must be
free'd on task kill.
User changeable flags:
#define F_CRIT 0x01
This task may not be preempted. It will run until it
clears this flag, delays itself, calls the scheduler
explicitly, or waits for an event.
The event control blocks
All event control blocks have two optional fields:
"name" is only present if TSK_NAMED is enabled. It is a namerec
structure as explained above.
"flags" is only present if TSK_DYNAMIC is enabled. It contains
F_TEMP If this control block was allocated automatically,
and must be free'd on delete.
F_STTEMP If the buffer for this event was allocated auto-
matically, and must be free'd on delete (pipes and
buffers only).
CTask Manual Version 1.1 88-07-01 Page 28
The flag event structure
Contains two queues for processes waiting on a flag state (clear
or set), plus the flag state (0 = clear, 1 = set).
typedef struct {
tcbptr wait_set;
tcbptr wait_clear;
int state;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} flag;
typedef flag far *flagptr;
The counter event structure
Similar to a flag, but contains a doubleword state counter.
typedef struct {
tcbptr wait_set;
tcbptr wait_clear;
dword state;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} counter;
typedef counter far *counterptr;
The resource event structure
Contains a queue for the tasks waiting for access to the resource,
a pointer to the current owner of the resource (to check for
illegal "release_resource" calls), and the resource state (0 = in
use, 1 = free).
typedef struct {
tcbptr waiting;
tcbptr owner;
int state;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} resource;
typedef resource far *resourceptr;
CTask Manual Version 1.1 88-07-01 Page 29
The mailbox event structure
The msgptr type is only used internally to chain mail blocks into
the mailbox. The mailbox type contains a queue of the tasks
waiting for mail, and a first and last pointer for the chain of
mail blocks.
struct msg_header {
struct msg_header far *next;
};
typedef struct msg_header far *msgptr;
typedef struct {
tcbptr waiting;
msgptr mail_first;
msgptr mail_last;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} mailbox;
typedef mailbox far *mailboxptr;
The pipe and word pipe event structure
Contains queues of the tasks waiting to read or write to the pipe,
indices for reading (outptr) and writing (inptr) into the buffer,
the buffer size, the number of bytes or words currently in the
pipe ("filled"), and the pointer to the buffer. A word pipe is
handled exactly the same as a byte pipe, the only difference being
the element size placed in the buffer. With normal pipes, charac-
ters are buffered, with word pipes, words.
typedef struct {
tcbptr wait_read;
tcbptr wait_write;
tcbptr wait_clear;
word bufsize;
word filled;
word inptr;
word outptr;
byteptr contents;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} pipe;
typedef pipe far *pipeptr;
CTask Manual Version 1.1 88-07-01 Page 30
typedef struct {
tcbptr wait_read;
tcbptr wait_write;
tcbptr wait_clear;
word bufsize;
word filled;
word inptr;
word outptr;
wordptr wcontents;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} wpipe;
typedef wpipe far *wpipeptr;
The buffer event structure
Contains resources for read and write access, the word pipe used
for buffering, and a message counter.
typedef struct {
resource buf_write;
resource buf_read;
wpipe pip;
word msgcnt;
byte flags; (if TSK_DYNAMIC)
namerec name; (if TSK_NAMED)
} buffer;
typedef buffer far *bufferptr;
NOTE: When modifying CTask structures, take care to modify the
equivalent definitions in the assembler include file "tsk.mac".
Some of the assembler routines have to use field offsets into
pointers, so having different offsets in C and assembler will
crash the system.
CTask Manual Version 1.1 88-07-01 Page 31
CTask Routines
==============
Installation and Removal
void install_tasker (int varpri, int speedup);
Installs the multitasker. Must be called prior to any other
routine. The calling routine is defined as the main task, and
assigned the highest priority. To allow other tasks to
execute, the main task must have its priority reduced, be
delayed, or wait on an event.
The "varpri" parameter enables variable priority if nonzero.
The "speedup" parameter is defined for the IBM PC/XT/AT as
the clock tick speedup factor. The timer tick frequency will
be set to
speedup ticks/sec msecs/tick
0 18.2 54.9 (normal clock)
1 36.4 27.5
2 72.8 13.7
3 145.6 6.9
4 291.3 3.4
Note that all timeouts are specified in tick units, so
changing the speedup parameter will influence timeouts and
delays. You can enable CLOCK_MSECS in tskconf.h to allow
timeouts to be specified in milliseconds.
The system clock will not be disturbed by changing the speed.
Using values above 2 can lead to interrupt overruns on slower
machines and is not recommended.
void remove_tasker (void);
Uninstalls the multitasker. Must only be called from the main
task, and should be called before exiting the program.
Miscellaneous
void preempt_off (void);
Disables task preemption. This is the default after installa-
tion. With preemption turned off, only delays, event waits,
and explicit scheduler calls will cause a task switch.
CTask Manual Version 1.1 88-07-01 Page 32
void preempt_on (void);
Enables task preemption. Task switches will occur on timer
ticks.
void far schedule (void);
Explicit scheduling request. The highest priority eligible
task will be made running.
void far c_schedule (void);
Conditional scheduling request. Scheduling will take place
only if preemption is allowed.
void tsk_dis_preempt (void)
Temporarily disable task preemption. Preemption will be re-
enabled by tsk_ena_preempt or an unconditional scheduler
call.
void tsk_ena_preempt (void)
Re-enable task preemption. Note that tsk_dis_preempt and
tsk_ena_preempt do not change the global preemption state set
by preempt_off and preempt_on.
int tsk_dis_int (void)
Disable interrupts. Returns the state of the interrupt flag
prior to this call (1 if interrupts were enabled).
void tsk_ena_int (int state)
Enables interrupts if "state" is nonzero. Normally used in
conjunction with tsk_dis_int.
The routines tsk_dis_int and tsk_ena_int may be used in a simpli-
fied scheme with the defines
CRITICAL; Declares "int crit_intsav;".
C_ENTER; Expands to "crit_intsav = tsk_dis_int ();"
C_LEAVE; Expands to "tsk_ena_int (crit_intsav);".
void tsk_cli (void)
Disables interrupts (intrinsic function).
CTask Manual Version 1.1 88-07-01 Page 33
void tsk_sti (void)
Unconditionally enables interrupts (intrinsic function).
void tsk_outp (int port, byte b)
Outputs the value "b" to hardware-port "port" (intrinsic
function).
byte tsk_inp (int port)
Returns the value read from port "port" (intrinsic function).
The following entry points may be used from assembler routines:
extrn scheduler: far
Direct entry into the scheduler. The stack must be set up as
for an interrupt handler, e.g.
pushf
cli
call scheduler
extrn _sched_int: far
Conditional scheduling call. The stack must be set up as for
an interrupt handler.
CTask Manual Version 1.1 88-07-01 Page 34
Task Operations
tcbptr create_task (tcbptr task, funcptr func, byteptr stack,
word stksz, word prior, farptr arg
[, byteptr name]);
Initialises a task. Must be called prior to any other opera-
tions on this task. The task is in the stopped state after
creation. It must be started to be able to run.
"task" is a pointer to a tcb (NULL for automatic allo-
cation).
"func" is a pointer to a void far function, with a single
dword sized parameter.
"stack" is a pointer to a stack area for the task (NULL for
automatic allocation).
"stksz" is the size of the stack area in bytes.
"prior" is the tasks priority (0 lowest, 0xffff highest).
"arg" is an argument to the created task. It may be used
to differentiate between tasks when using the same
function in different tasks.
"name" is the name of the task, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
void kill_task (tcbptr task);
Kills a task. The task can no longer be used.
int start_task (tcbptr task);
Starts a task, i.e. makes it eligible for running.
Returns 0 if task was started, -1 if the task was not
stopped.
A value of NULL for "task" will start the "main task".
int wake_task (tcbptr task);
Prematurely wakes a delayed or waiting task.
If the task was waiting for an event, it will be removed from
the waiting queue, with the operation terminating with an
error return value.
Returns 0 if task was waked, -1 if the task was not delayed
or waiting.
A value of NULL for "task" will wake the "main task".
CTask Manual Version 1.1 88-07-01 Page 35
void set_priority (tcbptr task, word prior)
Sets the priority of the specified task to "prior".
Note that you should NOT modify the priority field in the tcb
structure directly.
A value of NULL for "task" will set the priority of the "main
task".
void set_task_flags (tcbptr task, byte flags)
Sets the flags of the task to "flags". Currently, the only
flag that can be changed is
F_CRIT the task can not be preempted if set.
Note that you may NOT modify the flag field in the tcb
structure directly.
A value of NULL for "task" will set the flags of the "main
task".
Timer Operations
Timeouts are normally specified in timer ticks. If CLOCK_MSECS is
enabled, timeouts will be converted to the nearest number of timer
ticks automatically. The global word variable "ticks_per_sec"
contains a rough estimate of the number of ticks per second even
if CLOCK_MSECS is not enabled.
Event wait Timeouts
When waiting for an event, a timeout may be specified. The
operation will terminate with an error return value if the timeout
is reached before the event occurs.
NOTE that the timeout parameter is not optional. To specify no
timeout, use the value 0.
Delays
int t_delay (dword ticks);
Delay the current task for "ticks" clock ticks/milliseconds.
If ticks is 0, the task is stopped.
Timed Events
You can create timer control blocks to
- set a flag
- increment a counter
CTask Manual Version 1.1 88-07-01 Page 36
- wake up a task
- call a function
after a specified timeout interval. The operation may optionally
be repeated. For calling functions on a timeout, the following
must be noted:
Timeout functions should be as short as possible, since they
run at the highest priority.
The stack and data area is the area of the timer task. Be
careful when referencing variables.
Timeout functions may not use any functions that could cause
the timer task to be made waiting.
tlinkptr create_timer (tlinkptr elem, dword tout, farptr strucp,
byte kind, byte rept);
Create a timer control block.
Returns the address of the control block, NULL on error.
"elem" is the control block to initialise (NULL for auto-
matic allocation).
"tout" specifies the timeout interval.
"strucp" points to the structure to be used. This is a
flagptr for setting a flag, a counterptr for
increasing counters, a tcbptr for waking a task, or
a funcptr for calling a function.
"kind" gives the kind of structure "strucp" points to. It
must be one of
TKIND_WAKE for task-wakeup
TKIND_PROC for function call
TKIND_FLAG for flag set
TKIND_COUNTER for counter increment.
"rept" if nonzero, the action will be repeated on every
"tout" timeout interval.
NOTE: Timer control blocks can not be named.
void delete_timer (tlinkptr elem);
Deletes a timer control block, and removes it from the
timeout queue.
void change_timer (tlinkptr elem, dword tout, byte rept);
Changes the timeout value and/or the repeat flag for an
existing timer control block. If "tout" is zero, the call has
the same effect as delete_timer.
NOTE: delete_timer and change_timer should not be used on auto-
CTask Manual Version 1.1 88-07-01 Page 37
matically allocated timer control blocks. Since such blocks are
deallocated once the timeout expires (except for repeat
operations), the validity of the pointer is not guaranteed.
Event Operations
================
Resources
A Resource is either in use or free, the default state is free.
If a task has requested a resource, its state is in use.
Tasks requesting a resource while it is in use will be made
waiting. If the resource is released, the highest priority waiting
task is made eligible, and is assigned the resource. Interrupt
handlers may not use resource functions other than
"check_resource".
resourceptr create_resource (resourceptr rsc [, byteptr name]);
This initialises a resource. Must be used prior to any other
operations on a resource.
Returns the address of the control block, NULL on error.
"rsc" is the resource control block (NULL for automatic
allocation).
"name" is the name of the resource, up to eight
characters, plus zero-terminator. This parameter is
defined only if TSK_NAMEPAR is enabled in tskconf.h.
void delete_resource (resourceptr rsc);
Calling this routine is optional. It will kill all tasks
waiting for the resource.
void release_resource (resourceptr rsc);
Release the resource. If the task does not own the resource,
the call is ignored.
If tasks are waiting, the highest priority task will gain
access to the resource.
int request_resource (resourceptr rsc, dword timeout);
Requests the resource. If it is not available, the task is
suspended. A timeout may be specified.
Returns 0 if resource was allocated, -1 if a timeout
occurred, -2 on wake.
CTask Manual Version 1.1 88-07-01 Page 38
This call is ignored (returns a 0) if the calling task
already owns the resource.
int c_request_resource (resourceptr rsc);
Requests the resource only if it is available.
Returns 0 if resource was allocated, -1 if unavailable.
int check_resource (resourceptr rsc);
Returns 0 if resource is allocated, 1 if free.
Flags
A Flag can be either on or off, the default state is off (0).
Tasks can wait on either state of the flag. If the state is
changed, all tasks waiting for the state are made eligible.
Interrupt handlers may use the "set_flag", "clear_flag", and
"check_flag" functions.
flagptr create_flag (flagptr flg [, byteptr name]);
This initialises a flag. Must be used prior to any other
operations on a flag. The state is set to 0.
Returns the address of the control block, NULL on error.
"flg" is the flag control block (NULL for automatic allo-
cation).
"name" is the name of the flag, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
void delete_flag (flagptr flg);
Calling this routine is optional. It will kill all tasks
waiting for the flag.
void set_flag (flagptr flg);
This sets the flag. All tasks waiting for the set state will
be made eligible for running.
CTask Manual Version 1.1 88-07-01 Page 39
void clear_flag (flagptr flg);
This clears the flag. All tasks waiting for the clear state
will be made eligible for running.
int wait_flag_set (flagptr flg, dword timeout);
Waits for the set state of the flag. If the flag is not set,
the task is suspended. A timeout may be specified.
Returns 0 if the flag was set, -1 on timeout, -2 on wake.
int wait_flag_clear (flagptr flg, dword timeout);
Waits for the clear state of the flag. If the flag is not
clear, the task is suspended. A timeout may be specified.
Returns 0 if the flag was cleared, -1 on timeout, -2 on wake.
int clear_flag_wait_set (flagptr flg, dword timeout)
Combines the operations clear_flag and wait_flag_set.
Returns 0 if the flag was set, -1 on timeout, -2 on wake.
int check_flag (flagptr flg);
Returns 0 if flag clear, 1 if set.
Counters
A Counter can have any value from 0L to 0xffffffffL, the default
value is 0. Tasks can wait for a counter being zero or non-zero.
If the counter is cleared or decremented to zero, all tasks wai-
ting for the zero condition are made eligible.
If the counter is incremented, the highest priority task waiting
for non-zero is made eligible, and the counter is decremented by
one.
Interrupt handlers may use the "clear_counter", "inc_counter", and
"check_counter" functions.
counterptr create_counter (counterptr cnt [, byteptr name]);
This initialises a counter. Must be used prior to any other
operations on a flag. The value is set to 0.
Returns the address of the control block, NULL on error.
"cnt" is the counter control block (NULL for automatic
allocation).
CTask Manual Version 1.1 88-07-01 Page 40
"name" is the name of the counter, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
void delete_counter (counterptr cnt);
Calling this routine is optional. It will kill all tasks
waiting for the counter.
void clear_counter (counterptr cnt);
Clears the counter to zero. All tasks waiting for the zero
state will be made eligible for running.
int wait_counter_set (counterptr cnt, dword timeout);
Waits for the counter having a nonzero value. If the value is
zero, the task is suspended. The value is decremented when
the task gets access to the counter. A timeout may be speci-
fied.
Returns 0 if the counter was nonzero, -1 on timeout, -2 on
wake.
int wait_counter_clear (counterptr cnt, dword timeout);
Waits for the counter having a zero value. If the value is
nonzero, the task is suspended. A timeout may be specified.
Returns 0 if the counter was zero, -1 on timeout, -2 on wake.
void inc_counter (counterptr cnt);
Increments the counter. If tasks are waiting for the nonzero
state, the highest priority task is given access to the
counter.
dword check_counter (counterptr cnt);
Returns the current value of the counter.
CTask Manual Version 1.1 88-07-01 Page 41
Mailboxes
A Mailbox can hold any number of mail blocks.
Tasks can send mail to a mailbox and wait for mail to arrive. A
mail block is assigned to the highest priority waiting task. Care
must be exercised not to re-use a mail block until it has been
processed by the receiving task. The mail block format is user
defineable, with the first doubleword in a block reserved for the
tasking system. Interrupt handlers may use the "send_mail",
"c_wait_mail", and "check_mailbox" functions.
Note that mailboxes are well suited to provide the mechanism for
managing a chain of (equally sized) free mail blocks. On initiali-
sation, all free blocks would be "sent" to the manager box.
Requesting a free block would use "wait_mail", and freeing a used
block would again "send" it to the manager mailbox.
mailboxptr create_mailbox (mailboxptr box [, byteptr name]);
This initialises a mailbox. Must be used prior to any other
operations on a mailbox.
Returns the address of the control block, NULL on error.
"box" is the mailbox control block (NULL for automatic
allocation).
"name" is the name of the mailbox, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
void delete_mailbox (mailboxptr box);
Calling this routine is optional. It will kill all tasks
waiting for mail.
void send_mail (mailboxptr box, farptr msg);
Sends a message to the specified mailbox. If tasks are wai-
ting for mail, the highest priority task will get it.
farptr wait_mail (mailboxptr box, dword timeout);
Waits for mail. If no mail is available, the task is suspen-
ded. A timeout may be specified.
Returns the pointer to the received mail block, TTIMEOUT
(-1) on timeout, TWAKE (-2) on wake.
CTask Manual Version 1.1 88-07-01 Page 42
farptr c_wait_mail (mailboxptr box);
Reads mail only if mail is available.
Returns NULL if there is no mail, else a pointer to the
received message.
int check_mailbox (mailboxptr box);
Returns 0 if mailbox is empty, 1 otherwise.
Pipes
A Pipe has a buffer to hold character or word sized items.
An item may be written to a pipe if there is space in the buffer.
Otherwise, the writing task will be made waiting. A reading task
will be made waiting if the buffer is empty. If an item has been
read, the highest priority task waiting to write to the pipe will
be allowed to write.
Interrupt handlers may only use pipe functions "check_pipe",
"c_write_pipe", and "c_read_pipe".
Note that the values -1 and -2 (0xffff and 0xfffe) should be
avoided when writing to word pipes. These values are used to mark
timeout and wake when reading, or pipe empty when checking a word
pipe, and thus may lead to erroneous operation of your routines.
pipeptr create_pipe (pipeptr pip, farptr buf, word bufsize
[, byteptr name]);
wpipeptr create_wpipe (wpipeptr pip, farptr buf, word bufsize
[, byteptr name]);
This initialises a pipe. Must be used prior to any other
operations on a pipe. "bufsize" specifies the buffer size in
bytes. With word pipes, the buffer size should be divisible
by two.
Returns the address of the control block, NULL on error.
"pip" is the pipe/wpipe control block (NULL for automatic
allocation).
"name" is the name of the pipe, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
CTask Manual Version 1.1 88-07-01 Page 43
void delete_pipe (pipeptr pip);
void delete_wpipe (wpipeptr pip);
Calling this routine is optional. It will kill all tasks
waiting to read or write messages.
int read_pipe (pipeptr pip, dword timeout);
word read_wpipe (wpipeptr pip, dword timeout);
Read an item from a pipe. If no item is available, the task
is suspended. A timeout may be specified.
Returns the item, or -1 on timeout, -2 on wake.
int c_read_pipe (pipeptr pip);
word c_read_wpipe (wpipeptr pip);
Reads an item from a pipe only if one is available.
Returns the received item, or -1 if none is available.
int write_pipe (pipeptr pip, byte ch, dword timeout);
int write_wpipe (wpipeptr pip, word ch, dword timeout);
Writes an item to a pipe. If the buffer is full, the task is
suspended. A timeout may be specified.
If tasks are waiting to read, the item will be assigned to
the highest priority waiting task.
Returns 0 on success, or -1 on timeout, -2 on wake.
int c_write_pipe (pipeptr pip, byte ch);
int c_write_wpipe (wpipeptr pip, word ch);
Writes an item to a pipe only if enough space is available.
Returns 0 on success, or -1 if no space available.
int wait_pipe_empty (pipeptr pip, dword timeout);
int wait_wpipe_empty (wpipeptr pip, dword timeout);
Waits for the pipe to be emptied. If the pipe is already
empty on entry, the task continues to run. Returns 0 on
empty, -1 on timeout, -2 on wake.
int check_pipe (pipeptr pip);
word check_wpipe (pipeptr pip);
Returns -1 if the pipe is empty, else the first item in the
pipe. The item is not removed from the pipe.
CTask Manual Version 1.1 88-07-01 Page 44
word pipe_free (pipeptr pip);
word wpipe_free (wpipeptr pip);
Returns the number of free items available in the pipe.
Buffers
A Buffer has a buffer to hold message strings.
A message may be written to a buffer if it fits into the buffer.
Otherwise, the writing task will be made waiting. A reading task
will be made waiting if the buffer is empty. If a message has
been read, the highest priority task waiting to write to the
buffer will be allowed to write if its message fits into the
available space.
Interrupt handlers may not use buffer functions other than
"check_buffer".
The buffer routines are implemented using resources and pipes, and
thus are not part of the true "kernel" routines.
bufferptr create_buffer (bufferptr buf, farptr pbuf, word bufsize
[, byteptr name]);
This initialises a buffer. Must be used prior to any other
operations on a buffer.
The minimum buffer size is the length of the longest expected
message plus two.
Returns the address of the control block, NULL on error.
"buf" is the buffer control block (NULL for automatic
allocation).
"name" is the name of the buffer, up to eight characters,
plus zero-terminator. This parameter is defined
only if TSK_NAMEPAR is enabled in tskconf.h.
void delete_buffer (bufferptr buf);
Calling this routine is optional. It will kill all tasks
waiting to read or write messages.
CTask Manual Version 1.1 88-07-01 Page 45
int read_buffer (bufferptr buf, farptr msg, int size,
dword timeout);
Read a message from a buffer. If no message is available, the
task is suspended. A timeout may be specified.
The message will be copied to the buffer "buf", with a
maximum length of "size" bytes.
Returns the length of the received message, -1 on timeout, -2
on wake.
int c_read_buffer (bufferptr buf, farptr msg, int size);
Reads a message from a buffer only if one is available.
Returns the length of the received message, or -1 if none is
available.
int write_buffer (bufferptr buf, farptr msg, int size,
dword timeout);
Writes a message from "msg" with length "size" to a buffer.
If not enough space for the message is available, the task is
suspended. A timeout may be specified.
If tasks are waiting for messages, the highest priority task
will get it.
Returns the length of the message, -1 on timeout, -2 on wake,
-3 on error (length < 0 or length > buffer buffer size).
int c_write_buffer (bufferptr buf, farptr msg, int size);
Writes a message to a buffer only if enough space is
available.
Returns the length of the message, -1 if no space available,
or -3 on error (length < 0 or length > buffer buffer size).
word check_buffer (bufferptr buf);
Returns the current number of bytes (not messages) in the
buffer, including the length words.
CTask Manual Version 1.1 88-07-01 Page 46
The Keyboard Handler
====================
word t_read_key (void)
Waits for a key to be entered. Returns the ASCII-code in the
lower byte, and the scan-code in the upper byte, or -2
(0xfffe) on wake.
word t_wait_key (dword timeout)
Waits for a key to be entered. Returns the ASCII-code in the
lower byte, and the scan-code in the upper byte, or -1
(0xffff) on timeout, -2 (0xfffe) on wake.
word t_keyhit (void)
Returns -1 (0xffff) if no key was entered, else the value of
the key. The key is not removed.
The Serial I/O handler
======================
The serial I/O handler provides full duplex interrupt driven I/O
on the serial ports. Support for COM1 and COM2 is included, adding
other ports is possible by changing the source and/or defining
ports on-line.
int v24_define_port (int base, byte irq, byte vector)
Defines a new COM-port. This routine can only be used if
TSK_DYNAMIC is enabled.
"base" is the port base I/O address.
"irq" is the IRQ-line number (0-7 for XT, 0-15 for AT) the
port uses for interrupts.
"vector" is the interrupt vector number (0-0xff).
The return value is the internal port number for use with
v24_install. -1 is returned on error (memory full).
CTask Manual Version 1.1 88-07-01 Page 47
sioptr v24_install (int port, int init,
farptr rcvbuf, word rcvsize,
farptr xmitbuf, word xmitsize);
Installs the handler for the specified port. Currently, ports
0 (COM1) and 1 (COM2) are supported. Both ports may be used
simultaneously, the buffer areas for the ports must not be
shared.
"rcvbuf" is a word pipe buffer area for received characters,
with "rcvsize" specifying the buffer size in bytes. Note that
the buffer will hold rcvsize / 2 received characters.
"rcvbuf" may be NULL for automatic allocation.
"xmitbuf" is a byte pipe buffer for characters waiting for
transmissions, "xmitsize" gives its size in bytes.
"xmitbuf" may be NULL for automatic allocation.
If the port number for v24_install is ORed with 0x80, the
port is *relative*. This means that the entry in the BIOS
table for COM-Ports is used to search the tables internal to
the driver for the port information, instead of using the
table entry directly. If the port address cannot be found,
the driver returns with an error code. Note that ports are
numbered from 0, so to specify COM1, pass 0x80 as parameter.
If the "init" parameter is non-zero, the port is initialised
with the default values specified in the source. If the
parameter is zero, the control registers and baud rates on
entry are not modified.
The return value is a pointer to the sio control block for
use with the other driver routines. NULL is returned on error
(invalid port number, bad buffer sizes, nonexistent
hardware, out of memory).
void v24_remove (sioptr sio, int restore);
Removes the driver for the specified control block.
Should be called for all ports installed before exiting the
program.
If the "restore" parameter is nonzero, the control registers
and the baud rate that were set before installation of the
driver are restored. If the parameter is zero, the values
are not changed.
CTask Manual Version 1.1 88-07-01 Page 48
void v24_remove_all (void)
Removes all installed serial i/o drivers. This routine is
automatically called on remove_tasker if drivers were
installed. Note that you can not specify a restore parameter,
the default restore parameter is used (constant in tsksio.c).
void v24_change_rts (sioptr sio, int on);
Changes the state of the RTS output line. A nonzero value for
"on" will turn the output on.
void v24_change_dtr (sioptr sio, int on);
Changes the state of the DTR output line. A nonzero value for
"on" will turn the output on.
extern void far v24_change_baud (sioptr sio, long rate);
Changes the baud rate. The following baud rates are suppor-
ted:
50, 75, 110, 134, 150, 300, 600, 1200, 1800, 2000,
2400, 3600, 4800, 7200, 9600, 19200L, 38400L.
Note that baud rates above 9600 may cause problems on slow
machines.
extern void far v24_change_parity (sioptr sio, int par);
Changes parity. The parameter must be one of the following
values defined in "sio.h":
PAR_NONE no parity checks
PAR_EVEN even parity
PAR_ODD odd parity
PAR_MARK mark parity
PAR_SPACE space parity
extern void far v24_change_wordlength (sioptr sio, int len);
Changes word length. Values 5, 6, 7, 8 may be given.
extern void far v24_change_stopbits (sioptr sio, int n);
Changes Stopbits. Values 1 and 2 are allowed.
CTask Manual Version 1.1 88-07-01 Page 49
extern void far v24_watch_modem (sioptr sio, byte flags);
The modem status input lines specified in the "flags"
parameter must be active to allow transmission. Transmission
will stop if one of the lines goes inactive. The parameter
may be combined from the following values defined in "sio.h":
CTS to watch clear to send
DSR to watch data set ready
RI to watch ring indicator
CD to watch carrier detect
A value of zero (the default) will allow transmission
regardless of modem status.
extern void far v24_protocol (sioptr sio, int prot,
word offthresh, word onthresh);
Sets the handshake protocol to use. The "prot" parameter may
be combined from the following values:
XONXOFF to enable XON/XOFF (DC1/DC3) handshake
RTSCTS to enable RTS/CTS handshake
The "offthresh" value specifies the minimum number of free
items in the receive buffer. If this threshold is reached, an
XOFF is transmitted and/or the RTS line is inactivated.
The "onthresh" value specifies the minimum number of items
that must be free before XON is transmitted and/or the RTS
line is re-activated.
Enabling XONXOFF will remove all XON and XOFF characters from
the input stream. Transmission will be disabled when XOFF is
received, and re-enabled when XON is received.
Enabling RTSCTS will stop transmission when the CTS modem
input line is inactive.
int v24_send (sioptr sio, byte ch, dword timeout);
Transmits the character "ch". A timeout may be specified.
Returns -1 on timeout, -2 on wake, else 0.
CTask Manual Version 1.1 88-07-01 Page 50
int v24_receive (sioptr sio, dword timeout);
Waits for a received character. A timeout may be
specified. Returns -1 on timeout, -2 on wake, else the
character in the lower byte, plus an error code in the
upper byte. The error code is the combination of
0x02 overrun error
0x04 parity error
0x08 framing error
0x10 break interrupt.
int v24_check (sioptr sio);
Returns -1 if no receive character is available, else the
next character from the pipe. The character is not
removed.
int v24_overrun (sioptr sio);
Checks for receive pipe overrun. Returns 1 if an overrun
occurred, 0 otherwise. Clears the overrun flag.
int v24_modem_status (sioptr sio);
Returns the current modem status word. The CTS, DSR, RI,
and CD defines may be used to extract the modem input
line status.
int v24_complete (sioptr sio);
Returns 1 if all characters in the transmit pipe have been
sent, else 0.
int v24_wait_complete (sioptr sio, dword timeout);
Waits for the transmit pipe to be empty. Returns -1 on
timeout, -2 on wake, else 0.
CTask Manual Version 1.1 88-07-01 Page 51
The Printer Output Driver
=========================
The printer output driver provides for buffered output to up to
three printer ports (more can be added by editing the source).
Interrupt or polling may be selected. Due to the usual hardware
implementation of printers and the printer interface, using
polling is recommended. When using interrupts, you should not
simultaneously install both port 0 and port 1 with interrupt
enabled, since both ports share the same interrupt line.
int prt_install (int port, byte polling, word prior,
farptr xmitbuf, word xmitsize);
Installs the printer driver for the specified port. Ports 0
(LPT1), 1 (LPT2), and 2 (LPT3) are supported.
The "polling" parameter specifies polling output when
nonzero, interrupt output when zero.
The "prior" parameter sets the priority of the printer output
task.
"xmitbuf" is a buffer area for the printer output buffer,
"xmitsize" specifies its size in bytes.
The buffer pointer may be specified as NULL for dynamic
allocation if TSK_DYNAMIC is enabled.
If the port number for prt_install is ORed with 0x80, the
port is *relative*. This means that the entry in the BIOS
table for LPT-Ports is used to search the tables internal to
the driver for the port information, instead of using the
table entry directly. If the port address cannot be found,
the driver returns with an error code. Note that ports are
numbered from 0, so to specify LPT1, pass 0x80 as parameter.
The return value is the internal port number for use with the
other driver routines. -1 is returned on error (invalid port
number, bad buffer sizes, nonexistent hardware).
void prt_remove (int port);
Removes the printer driver for the specified port.
void prt_remove_all (void)
Removes all installed printer ports. This routine is auto-
matically called on remove_tasker if ports were installed.
CTask Manual Version 1.1 88-07-01 Page 52
void prt_change_control (int port, byte control);
Changes the printer control output lines of the port. The
value for "control" may be combined from the following values
defined in "prt.h":
AUTOFEED will enable printer auto feed if set
INIT will initialise (prime) the printer if clear
SELECT will select the printer if set
int prt_write (int port, byte ch, dword timeout);
Write a byte to the printer. A timeout may be given. Returns
0 on success, -1 on timeout, -2 on wake.
int prt_status (int port);
Returns the current printer status lines, combined from the
values
BUSY Printer is busy when 0
ACK Acknowledge (pulsed 0)
PEND Paper End detected when 0
SELIN Printer is selected (on line) when 1
ERROR Printer error when 0
int prt_complete (int port);
Returns 1 if the printer buffer has been completely transmit-
ted, 0 otherwise.
int prt_wait_complete (int port, dword timeout);
Waits for printer output to complete. Returns 0 on success,
else -1 on timeout, -2 on wake.